﻿#region COPYRIGHT© 2009-2013 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System;
using System.Diagnostics.Contracts;
using System.Globalization;
using FlitBit.Parse;

namespace FlitBit.Json
{
	public enum JsonDateConstantKind
	{
		None = 0,
		MicrosoftAjaxDotNet = 1,
		Iso9801 = 2,
	}

	public sealed class JsonStringLiteral : JsonValue
	{
		static readonly int CHashCodeSeed = typeof(JsonStringLiteral).AssemblyQualifiedName.GetHashCode();

		static readonly Match MSJsonDate = Match.MatchExact(CharRule.MatchExact(@"\/Date("),
																							Match.MatchRepeatedly(CharRule.DecimalDigits), CharRule.MatchExact(@")\/"));

		readonly string _value;

		public JsonStringLiteral(string value)
			: base(JsonNodeKind.StringLiteral)
		{
			Contract.Requires<ArgumentNullException>(value != null, "value cannot be null");

			_value = (value.Length > 1) ? value.UnquoteAny() : value;
			if (MSJsonDate.At(_value, 0))
			{
				DateKind = JsonDateConstantKind.MicrosoftAjaxDotNet;
			}
			else
			{
				DateTime parsed;
				if (DateTime.TryParseExact(_value, "u", null,
																	DateTimeStyles.AssumeUniversal | DateTimeStyles.AllowInnerWhite, out parsed))
				{
					DateKind = JsonDateConstantKind.Iso9801;
				}
			}
		}

		public JsonDateConstantKind DateKind { get; private set; }

		public override bool Equals(object obj)
		{
			return (obj is JsonStringLiteral) && Equals((JsonStringLiteral) obj);
		}

		public override int GetHashCode()
		{
			const int prime = 999067;
			var code = CHashCodeSeed * prime;
			code ^= ((null == this._value) ? 0 : this._value.GetHashCode()) * prime;
			return code;
		}

		public override TypeCode GetTypeCode()
		{
			return TypeCode.String;
		}

		public override bool ToBoolean(IFormatProvider provider)
		{
			return Convert.ToBoolean(_value);
		}

		public override byte ToByte(IFormatProvider provider)
		{
			return Convert.ToByte(_value);
		}

		public override char ToChar(IFormatProvider provider)
		{
			return Convert.ToChar(_value);
		}

		public override DateTime ToDateTime(IFormatProvider provider)
		{
			if (DateKind == JsonDateConstantKind.Iso9801)
			{
				DateTime parsed;
				if (DateTime.TryParseExact(_value, "u", null,
																	DateTimeStyles.AssumeUniversal | DateTimeStyles.AllowInnerWhite, out parsed))
				{
					return parsed;
				}
			}
			else if (DateKind == JsonDateConstantKind.MicrosoftAjaxDotNet)
			{
				return new DateTime(1970, 1, 1)
					.Add(TimeSpan.FromMilliseconds(double.Parse(_value.TakeAnyDigits())));
			}
			return Convert.ToDateTime(_value);
		}

		public override decimal ToDecimal(IFormatProvider provider)
		{
			return Convert.ToDecimal(_value);
		}

		public override double ToDouble(IFormatProvider provider)
		{
			return Convert.ToDouble(_value);
		}

		public override short ToInt16(IFormatProvider provider)
		{
			return Convert.ToInt16(_value);
		}

		public override int ToInt32(IFormatProvider provider)
		{
			return Convert.ToInt32(_value);
		}

		public override long ToInt64(IFormatProvider provider)
		{
			return Convert.ToInt64(_value);
		}

		public override sbyte ToSByte(IFormatProvider provider)
		{
			return Convert.ToSByte(_value);
		}

		public override float ToSingle(IFormatProvider provider)
		{
			return Convert.ToSingle(_value);
		}

		public override string ToString()
		{
			return _value;
		}

		public override string ToString(IFormatProvider provider)
		{
			return _value;
		}

		public override object ToType(Type conversionType, IFormatProvider provider)
		{
			switch (Type.GetTypeCode(conversionType))
			{
				case TypeCode.Boolean:
					return ToBoolean(provider);
				case TypeCode.Byte:
					return ToByte(provider);
				case TypeCode.Char:
					return ToChar(provider);
				case TypeCode.DateTime:
					return ToDateTime(provider);
				case TypeCode.Decimal:
					return ToDecimal(provider);
				case TypeCode.Double:
					return ToDouble(provider);
				case TypeCode.Int16:
					return ToInt16(provider);
				case TypeCode.Int32:
					return ToInt32(provider);
				case TypeCode.Int64:
					return ToInt64(provider);
				case TypeCode.Object:
					return _value;
				case TypeCode.SByte:
					return ToSByte(provider);
				case TypeCode.Single:
					return ToSingle(provider);
				case TypeCode.String:
					return _value;
				case TypeCode.UInt16:
					return ToUInt16(provider);
				case TypeCode.UInt32:
					return ToUInt32(provider);
				case TypeCode.UInt64:
					return ToUInt64(provider);
			}
			return base.ToType(conversionType, provider);
		}

		public override ushort ToUInt16(IFormatProvider provider)
		{
			return Convert.ToUInt16(_value);
		}

		public override uint ToUInt32(IFormatProvider provider)
		{
			return Convert.ToUInt32(_value);
		}

		public override ulong ToUInt64(IFormatProvider provider)
		{
			return Convert.ToUInt64(_value);
		}

		public bool Equals(JsonStringLiteral other)
		{
			return ((object) other != null)
				&& String.Equals(_value, other._value);
		}

		protected internal override void WriteFormatted(JsonFormatter formatter)
		{
			formatter.Write(String.Concat('"', _value.Replace("\"", "\\\""), '"'));
		}

		public static bool operator ==(JsonStringLiteral lhs, JsonStringLiteral rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs == null);
			}
			return lhs.Equals(rhs);
		}

		public static bool operator ==(JsonStringLiteral lhs, string rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs == null);
			}
			else
			{
				return String.Equals(lhs._value, rhs);
			}
		}

		public static bool operator ==(string lhs, JsonStringLiteral rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs == null);
			}
			else
			{
				return String.Equals(lhs, rhs._value);
			}
		}

		public static implicit operator String(JsonStringLiteral literal)
		{
			if ((object) literal == null)
			{
				return null;
			}
			return literal._value;
		}

		public static implicit operator JsonStringLiteral(string value)
		{
			return new JsonStringLiteral(value);
		}

		public static bool operator !=(JsonStringLiteral lhs, JsonStringLiteral rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs != null);
			}
			return !lhs.Equals(rhs);
		}

		public static bool operator !=(JsonStringLiteral lhs, string rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs != null);
			}
			return !String.Equals(lhs._value, rhs);
		}

		public static bool operator !=(string lhs, JsonStringLiteral rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs != null);
			}
			return !String.Equals(lhs, rhs._value);
		}

		internal static JsonStringLiteral ExpectStringLiteral(TokenizerState<JsonTokenKind> tokens)
		{
			var tk = tokens.ExpectTake(JsonTokenKind.StringLiteral);
			return new JsonStringLiteral(tk.Capture.Value);
		}
	}
}